<!---
   Copyright 2011 Andrew Myers

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--->
<cfcomponent name="ObjectCache" hint="A wrapper around the ColdFusion cfcache tag, which provides extra useful functionality.">

	<cffunction name="init" access="public" returntype="ObjectCache" hint="Object Constructor">
		<cfreturn this />
	</cffunction>

	<cffunction name="addObject" access="public" returntype="void" hint="Adds an object to the cache.">
		<cfargument name="objName" type="string" required="true" hint="The key under which this object will be stored in the cache."/>
		<cfargument name="objVal" type="any" required="true" hint="The value to store in the cache." />
		<cfargument name="cacheUntil" type="date" required="false" default="#CreateDate(3999,1,1)#" hint="The date at which this object should expire." />
		<cfargument name="createdDate" type="date" required="false" default="#now()#" hint="The date at which this data was created.  Can be set to a date in the past." />

		<cfset var timeNow = now()>
		<cfset var timeSpanDays  = DateDiff("d",timeNow, arguments.cacheUntil) />
		<cfset var timeSpanHours  = DateDiff("h",timeNow, arguments.cacheUntil) />
		<cfset var timeSpanMins  = DateDiff("n",timeNow, arguments.cacheUntil) />
		<cfset var timeSpanSecs  = DateDiff("s",timeNow, arguments.cacheUntil) />
		<cfset var temp = createObject("component", "CacheObjectWrapper").init(arguments.objVal, arguments.cacheUntil, arguments.createdDate)>

		<cfset var namespaceKey = '' />
		<cfset var newObjectName = arguments.objName />
		<cfif Find(".",arguments.objName) gt 0>
			<cfset var namespaceName = getNamespaceFromObjectName(arguments.objName) />
			<!--- Now get the current namespace key --->
			<cfset namespaceKey = getNamespaceKey(namespaceName) />
			<!--- Now create the object name --->
			<cfset newObjectName = namespaceKey & arguments.objName />
		</cfif>			
		<cfcache action="put" id="#newObjectName#" value="#temp#" 
			timespan="#CreateTimespan(timeSpanDays, timeSpanHours, timeSpanMins, timeSpanSecs)#">
	</cffunction>

	<cffunction name="retrieveObject" access="public" returntype="any" hint="Retrieves the object with the specified key from the cache.">
	    <cfargument name="objName" type="string" required="true" hint="The key of the object to retrieve." /> 
        <cfset var loc = {} /> 
        <cfset loc.objName = getObjName(arguments.objName)> 
	    <cfcache action="get" name="loc.returnVal" id ="#loc.objName#">
	    <cfif structKeyExists(loc, 'returnVal') and NOT loc.returnVal.isExpired()>
	    	<cfreturn loc.returnVal.getObjVal() />
	    <cfelse>
	    	<cfreturn '' />
	    </cfif>
	</cffunction>
    
    <cffunction name="getObjName" returntype="string">
        <cfargument name="objName" type="string">
        
		<cfset var returnVal = '' />
		<cfset var namespaceKey = '' />
		<cfset var newObjectName = '' />
		<cfset var namespaceName = getNamespaceFromObjectName(arguments.objName) />
		<!--- Now get the current namespace key --->
		<cfset namespaceKey = getNamespaceKey(namespaceName) />
		<!--- Now create the object name --->
		<cfset newObjectName = namespaceKey & arguments.objName />        
        <cfreturn newObjectName>
        
    </cffunction>

	<cffunction name="removeObject" access="public" returntype="void" output="false" hint="Remove an object from the cache.">
		<cfargument name="objName" type="string" required="true" hint="The key of the object to remove." />
		<cfcache action="flush" id="#arguments.objName#"></cfcache>
	</cffunction>

	<cffunction name="getNamespaceKey" access="public" returnType="any" output="true" hint="For internal use - prepares the key for the specified namespace.">
		<cfargument name="namespaceName" type="string" required="true" hint="The namespace for which we are to prepare the key." />
		<cfset var loc = {}>
		<cfcache action="get" name="loc.cachedObj" id="#arguments.namespaceName#">
		<!--- If not set, initialise it --->
		<cfif NOT structKeyExists(loc, 'cachedObj')>
			<cflock name="ocNamespaceKey" timeout="10" throwontimeout="true">
				<cfset loc.currentVal = RandRange(1, 10000) />
				<cfset loc.temp = addObject(objName = #arguments.namespaceName#, objVal = #loc.currentVal#) />
			</cflock>
		<cfelse>
			<cfset loc.currentVal = loc.cachedObj.getObjVal()>
		</cfif>
		<cfset loc.myKey = arguments.namespaceName & "_" & loc.currentVal & "_" />
		<cfreturn loc.myKey />
	</cffunction>

	<cffunction name="incrementNamespaceKey" access="public" returnType="any" output="true" hint="Increments the key for the specified namespace.">
		<cfargument name="namespaceName" type="string" required="true" hint="The namespace whose key we are to increment" />
		<cfset var loc = {} />
		<cfcache action="get" name="loc.cachedObj" id="#arguments.namespaceName#">
		<!--- If not set, initialise it --->
		<cfif structKeyExists(loc, 'cachedObj')>
			<cflock name="ocNamespaceKey" timeout="10" throwontimeout="true">
				<cfset loc.newVal = loc.cachedObj.getObjVal() + 1 />
				<cfset loc.temp = addObject(objName = #arguments.namespaceName#, objVal = #loc.newVal#) />
			</cflock>
		<cfelse>
			<!--- Create a new key --->
			<cfset loc.temp = getNamespaceKey(arguments.namespaceName) /> 
		</cfif>
	</cffunction>
	
	<cffunction name="expireCacheForPrefix" access="public" returnType="void" output="true" hint="Expires all objects in the cache with begine with the specified namespace prefix.">
		<cfargument name="prefix" type="string" required="true" />
		<cfset var temp = incrementNamespaceKey(arguments.prefix)>
	</cffunction>	

	<cffunction name="retrieveObjectIfCurrent" access="public" returntype="any">
		<cfargument name="objName" type="string" required="true" />
		<cfargument name="compareDate" type="date" required="true" />
		<cfset var loc = {} />
        <cfset loc.objName = getObjName(arguments.objName)> 
		<cfcache action="get" name="loc.temp" id="#loc.objName#">
		<cfif structKeyExists(loc, 'temp') AND NOT loc.temp.isCurrent(arguments.compareDate) AND NOT loc.temp.isExpired()>
			<cfreturn loc.temp.getObjVal()>
		<cfelse>
			<cfreturn "">
		</cfif>			
	</cffunction>

	<cffunction name="getNamespaceFromObjectName" access="public" returntype="string" hint="For internal use - looks for the first fullstop and returns the value that precedes it (if any)">
		<cfargument name="objName" type="string" required="true" hint="The key we are operating on." />
		<cfset var prefix = "" />
		<cfset var st1 = "">
		<!--- Anything preceding the first "." is the prefix --->
		<cfset st1 = ReFind("([^.]*)\.", arguments.objName, 1, "true") />
		<cfif st1.pos[1]>
			<cfset prefix = Mid(arguments.objName,st1.pos[2],st1.len[2]) />
		</cfif>
		<cfreturn prefix />
	</cffunction>

</cfcomponent>
